/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package twitterWatchFollow;

import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JTextArea;
import javax.swing.SwingWorker;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import twitter4j.RateLimitStatus;
import twitter4j.Twitter;
import twitter4j.TwitterException;
import twitter4j.User;

/**
 *
 * @author yos
 */
public class WatchFollowJFrame extends javax.swing.JFrame {
    public InformationJDialog m_infoDialog;
    public twitter4j.ResponseList m_blockList=null;
    //
    public static String m_currentUserTag="";
    public static int m_total_retry_loop_count=0;
    static int m_newFriendShipCount=0;

    /**
     * Creates new form WatchFollowJFrame
     */
    public WatchFollowJFrame() {
        initComponents();
        initChildDialog();
        setTitle("twitterWatchFollow --- configulation file : " + TwitterWatchFollow.conf_xml);
        ffview_jCheckBoxMenuItem.setEnabled(false);
        ffview_jCheckBoxMenuItem.setSelected(false);
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        jLabel_StatusBar = new javax.swing.JLabel();
        jScrollPane2 = new javax.swing.JScrollPane();
        jTextArea = new JTextArea(){
            @Override
            public void append(String str){
                super.append(str);
                jTextArea.setCaretPosition(jTextArea.getDocument().getLength());
                revalidate();
            }
        };
        jLabel_rateLimit = new javax.swing.JLabel();
        jLabel_progress = new javax.swing.JLabel();
        jMenuBar2 = new javax.swing.JMenuBar();
        jMenu3 = new javax.swing.JMenu();
        exitjMenuItem = new javax.swing.JMenuItem();
        SearchjMenu = new javax.swing.JMenu();
        Friend_FriendjCheckBoxMenuItem = new javax.swing.JCheckBoxMenuItem();
        Friend_FollowerjCheckBoxMenuItem = new javax.swing.JCheckBoxMenuItem();
        Follower_FriendjCheckBoxMenuItem = new javax.swing.JCheckBoxMenuItem();
        Follower_FollowerjCheckBoxMenuItem = new javax.swing.JCheckBoxMenuItem();
        jSeparator1 = new javax.swing.JPopupMenu.Separator();
        runjMenuItem = new javax.swing.JMenuItem();
        viewMenu = new javax.swing.JMenu();
        ffview_jCheckBoxMenuItem = new javax.swing.JCheckBoxMenuItem();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setPreferredSize(new java.awt.Dimension(1024, 400));

        jLabel_StatusBar.setText(" Idle ...");
        jLabel_StatusBar.setFocusable(false);

        jTextArea.setColumns(25);
        jTextArea.setRows(5);
        jScrollPane2.setViewportView(jTextArea);

        jLabel_rateLimit.setText("undef");
        jLabel_rateLimit.setFocusable(false);
        jLabel_rateLimit.setInheritsPopupMenu(false);
        jLabel_rateLimit.setRequestFocusEnabled(false);

        jLabel_progress.setText("progress");
        jLabel_progress.setFocusable(false);
        jLabel_progress.setInheritsPopupMenu(false);
        jLabel_progress.setRequestFocusEnabled(false);

        jMenu3.setText("File");

        exitjMenuItem.setText("exit");
        exitjMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                exitjMenuItemActionPerformed(evt);
            }
        });
        jMenu3.add(exitjMenuItem);

        jMenuBar2.add(jMenu3);

        SearchjMenu.setText("Search");

        Friend_FriendjCheckBoxMenuItem.setText("Friend->Friend");
        Friend_FriendjCheckBoxMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                Friend_FriendjCheckBoxMenuItemActionPerformed(evt);
            }
        });
        SearchjMenu.add(Friend_FriendjCheckBoxMenuItem);

        Friend_FollowerjCheckBoxMenuItem.setSelected(true);
        Friend_FollowerjCheckBoxMenuItem.setText("Friend->Follower");
        Friend_FollowerjCheckBoxMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                Friend_FollowerjCheckBoxMenuItemActionPerformed(evt);
            }
        });
        SearchjMenu.add(Friend_FollowerjCheckBoxMenuItem);

        Follower_FriendjCheckBoxMenuItem.setText("Follower->Friend");
        Follower_FriendjCheckBoxMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                Follower_FriendjCheckBoxMenuItemActionPerformed(evt);
            }
        });
        SearchjMenu.add(Follower_FriendjCheckBoxMenuItem);

        Follower_FollowerjCheckBoxMenuItem.setText("Follower->Follower");
        Follower_FollowerjCheckBoxMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                Follower_FollowerjCheckBoxMenuItemActionPerformed(evt);
            }
        });
        SearchjMenu.add(Follower_FollowerjCheckBoxMenuItem);
        SearchjMenu.add(jSeparator1);

        runjMenuItem.setText("run ...");
        runjMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                runjMenuItemActionPerformed(evt);
            }
        });
        SearchjMenu.add(runjMenuItem);

        jMenuBar2.add(SearchjMenu);

        viewMenu.setText("View");
        viewMenu.addMenuListener(new javax.swing.event.MenuListener() {
            public void menuCanceled(javax.swing.event.MenuEvent evt) {
            }
            public void menuDeselected(javax.swing.event.MenuEvent evt) {
            }
            public void menuSelected(javax.swing.event.MenuEvent evt) {
                viewMenuMenuSelected(evt);
            }
        });

        ffview_jCheckBoxMenuItem.setText("Friends/Followers only ...");
        ffview_jCheckBoxMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                ffview_jCheckBoxMenuItemActionPerformed(evt);
            }
        });
        viewMenu.add(ffview_jCheckBoxMenuItem);

        jMenuBar2.add(viewMenu);

        setJMenuBar(jMenuBar2);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(jScrollPane2)
            .addGroup(layout.createSequentialGroup()
                .addComponent(jLabel_StatusBar, javax.swing.GroupLayout.PREFERRED_SIZE, 336, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(jLabel_progress, javax.swing.GroupLayout.DEFAULT_SIZE, 226, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(jLabel_rateLimit, javax.swing.GroupLayout.PREFERRED_SIZE, 171, javax.swing.GroupLayout.PREFERRED_SIZE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 302, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(jLabel_StatusBar)
                    .addComponent(jLabel_rateLimit)
                    .addComponent(jLabel_progress)))
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void ffview_jCheckBoxMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_ffview_jCheckBoxMenuItemActionPerformed
        m_infoDialog.setVisible(!m_infoDialog.isVisible());
        ffview_jCheckBoxMenuItem.setSelected(m_infoDialog.isVisible());
    }//GEN-LAST:event_ffview_jCheckBoxMenuItemActionPerformed

    private void viewMenuMenuSelected(javax.swing.event.MenuEvent evt) {//GEN-FIRST:event_viewMenuMenuSelected
        ffview_jCheckBoxMenuItem.setSelected(m_infoDialog.isVisible());
    }//GEN-LAST:event_viewMenuMenuSelected

    private void Friend_FollowerjCheckBoxMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_Friend_FollowerjCheckBoxMenuItemActionPerformed
        Friend_FollowerjCheckBoxMenuItem.setSelected(!Friend_FollowerjCheckBoxMenuItem.isSelected());
    }//GEN-LAST:event_Friend_FollowerjCheckBoxMenuItemActionPerformed

    private void Friend_FriendjCheckBoxMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_Friend_FriendjCheckBoxMenuItemActionPerformed
        Friend_FriendjCheckBoxMenuItem.setSelected(!Friend_FriendjCheckBoxMenuItem.isSelected());
    }//GEN-LAST:event_Friend_FriendjCheckBoxMenuItemActionPerformed

    private void Follower_FriendjCheckBoxMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_Follower_FriendjCheckBoxMenuItemActionPerformed
        Follower_FriendjCheckBoxMenuItem.setSelected(!Follower_FriendjCheckBoxMenuItem.isSelected());
    }//GEN-LAST:event_Follower_FriendjCheckBoxMenuItemActionPerformed

    private void Follower_FollowerjCheckBoxMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_Follower_FollowerjCheckBoxMenuItemActionPerformed
        Follower_FollowerjCheckBoxMenuItem.setSelected(!Follower_FollowerjCheckBoxMenuItem.isSelected());
    }//GEN-LAST:event_Follower_FollowerjCheckBoxMenuItemActionPerformed

    private void runjMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_runjMenuItemActionPerformed
        try {
            applicationExecute();
        } catch (DatatypeConfigurationException | InterruptedException ex) {
            ex.printStackTrace();
        }
    }//GEN-LAST:event_runjMenuItemActionPerformed

    private void exitjMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exitjMenuItemActionPerformed
        this.dispose();
        System.exit(0);
    }//GEN-LAST:event_exitjMenuItemActionPerformed

    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        /*
         * Set the Nimbus look and feel
         */
        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
        /*
         * If Nimbus (introduced in Java SE 6) is not available, stay with the
         * default look and feel. For details see
         * http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
         */
        try {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException ex) {
            java.util.logging.Logger.getLogger(WatchFollowJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (InstantiationException ex) {
            java.util.logging.Logger.getLogger(WatchFollowJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            java.util.logging.Logger.getLogger(WatchFollowJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (javax.swing.UnsupportedLookAndFeelException ex) {
            java.util.logging.Logger.getLogger(WatchFollowJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        //</editor-fold>

        /*
         * Create and display the form
         */
        java.awt.EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new WatchFollowJFrame().setVisible(true);
            }
        });
    }
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JCheckBoxMenuItem Follower_FollowerjCheckBoxMenuItem;
    private javax.swing.JCheckBoxMenuItem Follower_FriendjCheckBoxMenuItem;
    private javax.swing.JCheckBoxMenuItem Friend_FollowerjCheckBoxMenuItem;
    private javax.swing.JCheckBoxMenuItem Friend_FriendjCheckBoxMenuItem;
    private javax.swing.JMenu SearchjMenu;
    private javax.swing.JMenuItem exitjMenuItem;
    private javax.swing.JCheckBoxMenuItem ffview_jCheckBoxMenuItem;
    protected javax.swing.JLabel jLabel_StatusBar;
    public javax.swing.JLabel jLabel_progress;
    public javax.swing.JLabel jLabel_rateLimit;
    private javax.swing.JMenu jMenu3;
    private javax.swing.JMenuBar jMenuBar2;
    private javax.swing.JScrollPane jScrollPane2;
    private javax.swing.JPopupMenu.Separator jSeparator1;
    public javax.swing.JTextArea jTextArea;
    private javax.swing.JMenuItem runjMenuItem;
    private javax.swing.JMenu viewMenu;
    // End of variables declaration//GEN-END:variables

    public JCheckBoxMenuItem getFollower_FollowerjCheckBoxMenuItem() {
        return Follower_FollowerjCheckBoxMenuItem;
    }

    public JCheckBoxMenuItem getFollower_FriendjCheckBoxMenuItem() {
        return Follower_FriendjCheckBoxMenuItem;
    }

    public JCheckBoxMenuItem getFriend_FollowerjCheckBoxMenuItem() {
        return Friend_FollowerjCheckBoxMenuItem;
    }

    public JCheckBoxMenuItem getFriend_FriendjCheckBoxMenuItem() {
        return Friend_FriendjCheckBoxMenuItem;
    }

    int showOkCancelDialog(double curratio) {
        int ans=JOptionPane.showConfirmDialog(this.getContentPane(),String.format("Rate Remining: %.3f %%", curratio*100.0)
                ,"comfirm",JOptionPane.OK_CANCEL_OPTION,JOptionPane.INFORMATION_MESSAGE);
        return ans;
    }
    
    private void initChildDialog(){
        m_infoDialog=new InformationJDialog(this, false);
        TwitterCommon.setWindowCenterPositionAtParent(this, m_infoDialog);
    }
    
    public void precheck(){
        xjcFollower.Root followerDocRoot=TwitterWatchFollow.m_followerHandler.getM_documentRoot();
        // uniq user list (remove duplicated)
        int totalRemoveCount=0;
        totalRemoveCount += TwitterCommon.uniquefyUserListById(followerDocRoot.getCurrentFollowers().getUser());
        totalRemoveCount += TwitterCommon.uniquefyUserListById(followerDocRoot.getCurrentFriends().getUser());
        totalRemoveCount += TwitterCommon.uniquefyUserListById(followerDocRoot.getKeep().getUser());
        totalRemoveCount += TwitterCommon.uniquefyUserListById(followerDocRoot.getPaused().getUser());
        totalRemoveCount += TwitterCommon.uniquefyUserListById(followerDocRoot.getRejected().getUser());
        totalRemoveCount += TwitterCommon.uniquefyUserListById(followerDocRoot.getBlock().getUser());
        totalRemoveCount += TwitterCommon.uniquefyUserListById(followerDocRoot.getWatchDog().getUser());
        jTextArea.append(String.format("pre-procedure: compressed duplicated elements: %d\n",totalRemoveCount));
        if(totalRemoveCount>0) TwitterWatchFollow.m_followerHandler.marshallerToFile();
        //
        setVisible(true);
        jTextArea.append("Idle...\n");
        //applicationExecute();
    }

    /** This methods is called by WatchFollowJFrame#run methods
     * 
     */
    public void applicationExecute() throws DatatypeConfigurationException, InterruptedException {
        SwingWorker<Boolean,String> worker = new SwingWorker<Boolean,String>(){
            @Override
            protected Boolean doInBackground() throws InterruptedException, DatatypeConfigurationException, IOException, ClassNotFoundException{
                int loopCount=-1;
                int applicationIndex=0;
                m_currentUserTag="";
                List<Boolean> isRunFriendsFollowersList=new ArrayList<>();
                isRunFriendsFollowersList.add(getFriend_FriendjCheckBoxMenuItem().isSelected());
                isRunFriendsFollowersList.add(getFriend_FollowerjCheckBoxMenuItem().isSelected());
                isRunFriendsFollowersList.add(getFollower_FriendjCheckBoxMenuItem().isSelected());
                isRunFriendsFollowersList.add(getFollower_FollowerjCheckBoxMenuItem().isSelected());
                do{
                    xjcConf.ApplicationType application=TwitterWatchFollow.m_confHandler.getM_documentRoot().getApplication().get(applicationIndex);
                    for(int i=0;i<isRunFriendsFollowersList.size();++i){
                        if(!isRunFriendsFollowersList.get(i)) continue;
                        isRunFriendsFollowersList.set(i, Boolean.FALSE);
                        ++loopCount;
                        applicationLoop(application, TwitterWatchFollow.m_confHandler.getM_documentRoot().getUser(), i, loopCount);
                        // // TODO: 400 --- rate limit
                        // //       401 --- maybe twitter internal problem (other users' tweet maybe read)
                        // marshaller
                        TwitterWatchFollow.m_followerHandler.marshallerToFile();
                        jTextArea.append(String.format("\napplication loop %d/%d finished\nxml output file '%s' ... done\n",loopCount+1
                                ,TwitterWatchFollow.m_confHandler.getM_documentRoot().getApplication().size()
                                ,TwitterWatchFollow.m_followerHandler.getM_xmlOutputFilePath()));
                    }
                }while(++applicationIndex<TwitterWatchFollow.m_confHandler.getM_documentRoot().getApplication().size());
                Calendar cal=Calendar.getInstance();
                String enddate=String.format("%04d/%02d/%02d %02d:%02d:%02d",
                cal.get(Calendar.YEAR),cal.get(Calendar.MONTH)+1,cal.get(Calendar.DAY_OF_MONTH),
                cal.get(Calendar.HOUR_OF_DAY),cal.get(Calendar.MINUTE),cal.get(Calendar.SECOND));
                
                publish("\nINFO: program finished in "+enddate+".\n");
                return true;
                
            }
            @Override
            protected void process(java.util.List<String> chunkList){
                for(String msg : chunkList){
                    jTextArea.append(msg);
                }
            }
        };
        worker.execute();
    }
    
    private int applicationLoop(xjcConf.ApplicationType application, String confUser, int mode, int loopCount) throws InterruptedException, DatatypeConfigurationException {
        if(application.isRun()){
            setTitle(application.getName());
            jLabel_StatusBar.setText(String.format("app: %s, user %s logged on.",application.getName(),confUser));
            xjcFollower.Root followerDocRoot=TwitterWatchFollow.m_followerHandler.getM_documentRoot();
            String follower_xml=TwitterWatchFollow.m_followerHandler.getM_xmlOutputFilePath();
            Twitter tw = TwitterCommon.logonByConsumerKey(confUser,application);
            while(m_blockList==null){
                try {
                    m_blockList=tw.getBlockingUsers();
                } catch (TwitterException ex) {
                    String msg=ex.getMessage();
                    if(ex.getStatusCode()==400){
                        if(!TwitterCommon.sleepUntilRateLimitReset(ex, this)){
                            return -1;
                        }
                        TwitterWatchFollow.m_total_retry_loop_count=-1;
                        continue;
                    }else{
                        JOptionPane.showMessageDialog(rootPane, msg, "Information", JOptionPane.WARNING_MESSAGE);
                        Logger.getLogger(WatchFollowJFrame.class.getName()).log(Level.SEVERE, null, ex);
                        return -1;
                    }
                }
                // TODO initial access to twitter
                for(int i=0;i<m_blockList.size();++i){
                    twitter4j.User blockUser=(twitter4j.User) m_blockList.get(i);
                    if(!TwitterCommon.isDuplicatedUserIdInXMLUserList(blockUser.getId(), followerDocRoot.getBlock().getUser())){
                        xjcFollower.User newblockuser=new xjcFollower.ObjectFactory().createUser();
                        newblockuser.setValue(blockUser.getId());
                        newblockuser.setName(blockUser.getName());
                        newblockuser.setScreenName(blockUser.getScreenName());
                        GregorianCalendar gc=new GregorianCalendar();
                        gc.setTime(Calendar.getInstance().getTime());
                        newblockuser.setDate(DatatypeFactory.newInstance().newXMLGregorianCalendar(gc));
                        followerDocRoot.getBlock().getUser().add(newblockuser);
                    }
                }
            }
            if(loopCount==0 && TwitterWatchFollow.m_total_retry_loop_count==0){
                double curratio=-1.0;
                try {
                    curratio=updateRateLimit(tw);
                } catch (TwitterException ex) {
                    Logger.getLogger(WatchFollowJFrame.class.getName()).log(Level.SEVERE, null, ex);
                }
                int ans=showOkCancelDialog(curratio);
                switch(ans){
                case JOptionPane.OK_OPTION:
                    break;
                default:
                    jTextArea.append("INFO: program terminated by user.\n");
                    return(-1);
                }
            }
            TwitterWatchFollow.m_total_retry_loop_count=0;
            
            User user=null;
            try {
                user=tw.showUser(confUser);
            } catch (TwitterException ex) {
                Logger.getLogger(WatchFollowJFrame.class.getName()).log(Level.SEVERE, null, ex);
            }
            if(user==null) return(-1);
            //
            jTextArea.append(String.format("target user: %s (Id: %d, screen_name: %s, Lang: %s)\n",user.getName(),user.getId(),user.getScreenName(),user.getLang()));
            //
            if(TwitterWatchFollow.m_isInit){
                TwitterWatchFollow.m_isInit=false;
                jTextArea.append(String.format("updating friends/followers XML database and procedure expired.\n"));
                try {
                    updateRateLimit(tw);
                    updateCurrentRelationship(tw, user, follower_xml, followerDocRoot);
                } catch (TwitterException ex) {
                    Logger.getLogger(WatchFollowJFrame.class.getName()).log(Level.SEVERE, null, ex);
                }
                //
                m_infoDialog.showFriendsFollowersRatioInCurrentXML(tw, this);
                m_infoDialog.setVisible(true);
                ffview_jCheckBoxMenuItem.setEnabled(true);
                ffview_jCheckBoxMenuItem.setSelected(true);
            }
            //
            GregorianCalendar gc=new GregorianCalendar();
            gc.setTime(Calendar.getInstance().getTime());
            
            int totalCount=0;
            List<xjcFollower.User> parentuserlist=null;
            if(mode==0 || mode==1){
                parentuserlist = TwitterWatchFollow.m_followerHandler.getM_documentRoot().getCurrentFriends().getUser();
            }else{
                parentuserlist = TwitterWatchFollow.m_followerHandler.getM_documentRoot().getCurrentFollowers().getUser();
            }
            // parent loop
            boolean isFollowerXmlUpdated=false;
            List<Integer> parentRandIndexList=TwitterCommon.createRandIndexList(parentuserlist.size());
            for(int parentloopcount=0;parentloopcount<parentuserlist.size();++parentloopcount){
                xjcFollower.User parent = parentuserlist.get(parentRandIndexList.get(parentloopcount));
                
                long parentId=parent.getValue();
                User parentShowUser=null;
                try {
                    parentShowUser=tw.showUser(parentId);
                } catch (TwitterException ex) {
                    if(!TwitterCommon.sleepUntilRateLimitReset(ex, this)){
                        Logger.getLogger(WatchFollowJFrame.class.getName()).log(Level.SEVERE, null, ex);
                        return -1;
                    }else{
                        continue;
                    }
                }
                if(!parentShowUser.getLang().equalsIgnoreCase("ja")){
                    break;
                }
                double xmlOutputRate;
                double prelimiter=1.0;
                TwitterCommon.FriendsFollowers childEnum=TwitterCommon.FriendsFollowers.FRIENDS;
                if(mode==1 || mode==3){
                    childEnum=TwitterCommon.FriendsFollowers.FOLLOWERS;
                }
                List<Long> relationList = null;
                try {
                    relationList = TwitterCommon.relationList(tw, parentId, childEnum, jLabel_progress);
                } catch (TwitterException ex) {
                    if(!TwitterCommon.sleepUntilRateLimitReset(ex, this)){
                        Logger.getLogger(WatchFollowJFrame.class.getName()).log(Level.SEVERE, null, ex);
                    }else{
                        break;
                    }
                }

                // child loop
                List<Integer> childRandIndexList=TwitterCommon.createRandIndexList(relationList.size());
                for(int childloopcount=0;childloopcount<relationList.size();++childloopcount){
                    long childId = relationList.get(childRandIndexList.get(childloopcount));
                    ++totalCount;
                    m_currentUserTag="";
                    double limiter=0;
                    try {
                        limiter=updateRateLimit(tw);
                    } catch (TwitterException ex) {
                        if(!TwitterCommon.sleepUntilRateLimitReset(ex, this)){
                            Logger.getLogger(WatchFollowJFrame.class.getName()).log(Level.SEVERE, null, ex);
                            return -1;
                        }else{
                            break;
                        }
                    }
                    xmlOutputRate=((double) Math.floor(limiter*10.0))/10.0;
                    jTextArea.append(String.format("checking %d from %d (screen name: %s) : ",childId,parentId,parent.getScreenName()));
                    // pre-checking (in XML preventing) before access tw
                    if(TwitterCommon.isUserIdPreventedInXML(childId, jTextArea)){
                        jTextArea.append(" ... skipped by XML pre-checkiing (already prevending)\n");
                        continue;                        
                    }
                    //
                    xjcFollower.User newelem = new xjcFollower.ObjectFactory().createUser();
                    newelem.setValue(childId);
                    User childShowUser=null;
                    try {
                        childShowUser=tw.showUser(childId);
                    } catch (TwitterException ex) {
                        if(!TwitterCommon.sleepUntilRateLimitReset(ex, this)){
                            Logger.getLogger(WatchFollowJFrame.class.getName()).log(Level.SEVERE, null, ex);
                            return -1;
                        }else{
                            break;
                        }
                    }
                    newelem.setName(childShowUser.getName());
                    newelem.setScreenName(childShowUser.getScreenName());
                    newelem.setDate(DatatypeFactory.newInstance().newXMLGregorianCalendar(gc));
                    m_currentUserTag=String.format("        <user screenName=\"%s\" name=\"%s\" date=\"%s\" reason=\"%s\">%d</user>"
                            ,newelem.getScreenName(),newelem.getName(),newelem.getDate(),xjcFollower.ReasonType.UNDEFINED_EXCEPTION,newelem.getValue());
                    double updateRate=-1.0;
                    try {
                        updateRate=TwitterCommon.getFriendShipRate(application, tw, newelem, childShowUser, jTextArea);
                    } catch (TwitterException ex) {
                        if(!TwitterCommon.sleepUntilRateLimitReset(ex, this)){
                            Logger.getLogger(WatchFollowJFrame.class.getName()).log(Level.SEVERE, null, ex);
                            return -1;
                        }else{
                            break;
                        }
                    }
                    if(updateRate<0.0){
                        jTextArea.append(" ... skipped by pre-rating (<0.0)\n");
                        newelem.setReason(xjcFollower.ReasonType.PRE_RATING);
                        TwitterWatchFollow.m_followerHandler.getM_documentRoot().getPaused().getUser().add(newelem);
                    }else if(updateRate>=TwitterWatchFollow.m_post_a_day_min && updateRate<=TwitterWatchFollow.m_post_a_day_max){
                        double rnd=Math.random();
                        if(rnd<TwitterWatchFollow.m_rand_add_ratio_max){
                            jTextArea.append(" ... add into a new Friend!\n");
                            try {
                                tw.createFriendship(childId, true);
                                isFollowerXmlUpdated=true;
                                String welcome_tweet=TwitterCommon.getReplyMessage(xjcConf.ReplyOrderType.NEW_FRIEND, "");
                                if(welcome_tweet!=null && welcome_tweet.length()>0){
                                    welcome_tweet = "@"+newelem.getScreenName()+" "+welcome_tweet;
                                    tw.updateStatus(welcome_tweet);
                                }
                            } catch (TwitterException ex) {
                                if(!TwitterCommon.sleepUntilRateLimitReset(ex, this)){
                                    Logger.getLogger(WatchFollowJFrame.class.getName()).log(Level.SEVERE, null, ex);
                                    return -1;
                                }else{
                                    break;
                                }
                            }
                            TwitterWatchFollow.m_followerHandler.getM_documentRoot().getWatchDog().getUser().add(newelem);
                            ++m_newFriendShipCount;
                        }else{
                            newelem.setReason(xjcFollower.ReasonType.RAND_OUT_BOUND);
                            TwitterWatchFollow.m_followerHandler.getM_documentRoot().getPaused().getUser().add(newelem);
                            jTextArea.append(String.format(" ... skip watchDog (rand exception %.3f > %.3f)\n",rnd,TwitterWatchFollow.m_rand_add_ratio_max));
                        }
                    }else if(updateRate<1.0){
                        jTextArea.append(String.format(" ... skip watchDog (low rating %.2f)\n",updateRate));
                        newelem.setReason(xjcFollower.ReasonType.LOW_RATING);
                        if(!TwitterCommon.isDuplicatedUserIdInXMLUserList(childId,TwitterWatchFollow.m_followerHandler.getM_documentRoot().getPaused().getUser())){
                            TwitterWatchFollow.m_followerHandler.getM_documentRoot().getPaused().getUser().add(newelem);
                        }
                    }else{
                        jTextArea.append(String.format(" ... skip watchDog (high rating %.2f)\n",updateRate));
                        newelem.setReason(xjcFollower.ReasonType.HIGH_RATING);
                        if(!TwitterCommon.isDuplicatedUserIdInXMLUserList(childId,TwitterWatchFollow.m_followerHandler.getM_documentRoot().getPaused().getUser())){
                            TwitterWatchFollow.m_followerHandler.getM_documentRoot().getPaused().getUser().add(newelem);
                        }
                    }
                    //
                    if((prelimiter+TwitterWatchFollow.EPS>xmlOutputRate && limiter-TwitterWatchFollow.EPS<xmlOutputRate) || limiter<0.05){
                        if(isFollowerXmlUpdated){
                            jTextArea.append(String.format("saved xml file '%s' at rating Limit %.3f\n",TwitterWatchFollow.m_followerHandler.getM_xmlOutputFilePath(),xmlOutputRate));
                            TwitterWatchFollow.m_followerHandler.marshallerToFile();
                            isFollowerXmlUpdated=false;
                        }
                        xmlOutputRate -= 0.1;
                    }
                    prelimiter=limiter;
                    jLabel_progress.setText(String.format("f/total=%d/%d",m_newFriendShipCount,totalCount));
                    TwitterCommon.sleepSupressWarnings(1000);
                    if(m_newFriendShipCount>TwitterWatchFollow.m_maximum_friend_add){
                        jTextArea.append(String.format("maximum friends add limit exceeded (>%d). escape from loop",
                                TwitterWatchFollow.m_maximum_friend_add));
                        break;
                    }
                }
                if(m_newFriendShipCount>TwitterWatchFollow.m_maximum_friend_add) break;
            }
            jTextArea.append(String.format("total %d user are added to watchdog as new friends.",m_newFriendShipCount));
            //
            //twitterCommon.isCreateFriendShip(application, tw, 152963467);
        }
        return(0);
    }

    private void updateCurrentRelationship(Twitter tw, User user, String follower_xml, xjcFollower.Root followerDocRoot) throws InterruptedException, TwitterException {
        jTextArea.append(String.format("  current friends: %d --- updating %s ... ", user.getFriendsCount(), follower_xml));
        updateRelationshipFriendsFollowers(tw, user, followerDocRoot.getCurrentFriends().getUser(), TwitterCommon.FriendsFollowers.FRIENDS);
        //
        jTextArea.append(String.format("  current followers: %d --- updating %s ... ", user.getFollowersCount(), follower_xml));
        updateRelationshipFriendsFollowers(tw, user, followerDocRoot.getCurrentFollowers().getUser(), TwitterCommon.FriendsFollowers.FOLLOWERS);
    }

    private void updateRelationshipFriendsFollowers(Twitter tw, User user, List<xjcFollower.User> xmlUserList
            ,TwitterCommon.FriendsFollowers friends_or_followers) throws InterruptedException, TwitterException {
        
        try {
            List<Long> relationList = TwitterCommon.relationList(tw, user.getId(), friends_or_followers, jLabel_progress);
        
            List<Long> removeXmlUserIdList=new ArrayList<>();
            List<xjcFollower.User> removeUserList=new ArrayList<>();
            for(xjcFollower.User curuser : xmlUserList){
                if(!relationList.contains(curuser.getValue())){
                    removeXmlUserIdList.add(curuser.getValue());
                    removeUserList.add(curuser);
                }
            }
            xmlUserList.removeAll(removeUserList);
            //
            List<Long> appendXmlUserIdList=new ArrayList<>();
            for(Long relationUserId : relationList){
                if(!TwitterCommon.isDuplicatedUserIdInXMLUserList(relationUserId, xmlUserList)){
                    appendXmlUserIdList.add(relationUserId);
                }
            }
            for (Long id : appendXmlUserIdList){
                User foo = tw.showUser(id);
                xjcFollower.User elem = new xjcFollower.ObjectFactory().createUser();
                elem.setValue(id);
                elem.setName(foo.getName());
                elem.setScreenName(foo.getScreenName());
                GregorianCalendar gc=new GregorianCalendar();
                gc.setTime(Calendar.getInstance().getTime());
                elem.setDate(DatatypeFactory.newInstance().newXMLGregorianCalendar(gc));
                xmlUserList.add(elem);
            }
            //
            jTextArea.append("done");
        } catch (TwitterException ex){
            jTextArea.append("TwitterEception occured");
            Logger.getLogger(WatchFollowJFrame.class.getName()).log(Level.SEVERE, null, ex);
        } catch (DatatypeConfigurationException ex){
            jTextArea.append("DatatypeConfigurationException occured");
            Logger.getLogger(WatchFollowJFrame.class.getName()).log(Level.SEVERE, null, ex);
        }
        jTextArea.append("\n");
    }

    public double updateRateLimit(Twitter tw) throws TwitterException{
        RateLimitStatus ratelimit = tw.getRateLimitStatus();
        int hourlyLimit=ratelimit.getHourlyLimit();
        int remainingHits=ratelimit.getRemainingHits();
        int secondsUntilReset=ratelimit.getSecondsUntilReset();
        double curratio=((double) remainingHits)/hourlyLimit;
        jLabel_rateLimit.setText(String.format("remain %.2f %%  %d sec\n",curratio*100.0,secondsUntilReset));
        jTextArea.append(String.format("INFO: rate limit remaining: %.2f %%, wait %d sec (= %.1f min)\n",curratio*100.0,secondsUntilReset,secondsUntilReset/60.0));
        return curratio;
    }

    
}
